// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/public/test/test_file_system_backend.h"

#include <set>
#include <string>
#include <utility>
#include <vector>

#include "base/files/file.h"
#include "base/files/file_util.h"
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/observer_list.h"
#include "base/sequenced_task_runner.h"
#include "base/threading/thread_task_runner_handle.h"
#include "storage/browser/fileapi/copy_or_move_file_validator.h"
#include "storage/browser/fileapi/file_observers.h"
#include "storage/browser/fileapi/file_stream_reader.h"
#include "storage/browser/fileapi/file_system_operation.h"
#include "storage/browser/fileapi/file_system_operation_context.h"
#include "storage/browser/fileapi/file_system_quota_util.h"
#include "storage/browser/fileapi/local_file_util.h"
#include "storage/browser/fileapi/native_file_util.h"
#include "storage/browser/fileapi/quota/quota_reservation.h"
#include "storage/browser/fileapi/sandbox_file_stream_writer.h"
#include "storage/browser/quota/quota_manager.h"
#include "storage/common/fileapi/file_system_util.h"

using storage::FileSystemContext;
using storage::FileSystemOperation;
using storage::FileSystemOperationContext;
using storage::FileSystemURL;

namespace content {

namespace {

    // Stub implementation of storage::LocalFileUtil.
    class TestFileUtil : public storage::LocalFileUtil {
    public:
        explicit TestFileUtil(const base::FilePath& base_path)
            : base_path_(base_path)
        {
        }
        ~TestFileUtil() override { }

        // LocalFileUtil overrides.
        base::File::Error GetLocalFilePath(FileSystemOperationContext* context,
            const FileSystemURL& file_system_url,
            base::FilePath* local_file_path) override
        {
            *local_file_path = base_path_.Append(file_system_url.path());
            return base::File::FILE_OK;
        }

    private:
        base::FilePath base_path_;
    };

} // namespace

// This only supports single origin.
class TestFileSystemBackend::QuotaUtil : public storage::FileSystemQuotaUtil,
                                         public storage::FileUpdateObserver {
public:
    QuotaUtil()
        : usage_(0)
    {
    }
    ~QuotaUtil() override { }

    // FileSystemQuotaUtil overrides.
    base::File::Error DeleteOriginDataOnFileTaskRunner(
        FileSystemContext* context,
        storage::QuotaManagerProxy* proxy,
        const GURL& origin_url,
        storage::FileSystemType type) override
    {
        NOTREACHED();
        return base::File::FILE_OK;
    }

    scoped_refptr<storage::QuotaReservation>
    CreateQuotaReservationOnFileTaskRunner(
        const GURL& origin_url,
        storage::FileSystemType type) override
    {
        NOTREACHED();
        return scoped_refptr<storage::QuotaReservation>();
    }

    void GetOriginsForTypeOnFileTaskRunner(storage::FileSystemType type,
        std::set<GURL>* origins) override
    {
        NOTREACHED();
    }

    void GetOriginsForHostOnFileTaskRunner(storage::FileSystemType type,
        const std::string& host,
        std::set<GURL>* origins) override
    {
        NOTREACHED();
    }

    int64_t GetOriginUsageOnFileTaskRunner(
        FileSystemContext* context,
        const GURL& origin_url,
        storage::FileSystemType type) override
    {
        return usage_;
    }

    // FileUpdateObserver overrides.
    void OnStartUpdate(const FileSystemURL& url) override { }
    void OnUpdate(const FileSystemURL& url, int64_t delta) override
    {
        usage_ += delta;
    }
    void OnEndUpdate(const FileSystemURL& url) override { }

private:
    int64_t usage_;
    DISALLOW_COPY_AND_ASSIGN(QuotaUtil);
};

TestFileSystemBackend::TestFileSystemBackend(
    base::SequencedTaskRunner* task_runner,
    const base::FilePath& base_path)
    : base_path_(base_path)
    , task_runner_(task_runner)
    , file_util_(
          new storage::AsyncFileUtilAdapter(new TestFileUtil(base_path)))
    , quota_util_(new QuotaUtil)
    , require_copy_or_move_validator_(false)
{
    update_observers_ = update_observers_.AddObserver(quota_util_.get(), task_runner_.get());
}

TestFileSystemBackend::~TestFileSystemBackend()
{
}

bool TestFileSystemBackend::CanHandleType(storage::FileSystemType type) const
{
    return (type == storage::kFileSystemTypeTest);
}

void TestFileSystemBackend::Initialize(FileSystemContext* context)
{
}

void TestFileSystemBackend::ResolveURL(const FileSystemURL& url,
    storage::OpenFileSystemMode mode,
    const OpenFileSystemCallback& callback)
{
    callback.Run(GetFileSystemRootURI(url.origin(), url.type()),
        GetFileSystemName(url.origin(), url.type()),
        base::File::FILE_OK);
}

storage::AsyncFileUtil* TestFileSystemBackend::GetAsyncFileUtil(
    storage::FileSystemType type)
{
    return file_util_.get();
}

storage::WatcherManager* TestFileSystemBackend::GetWatcherManager(
    storage::FileSystemType type)
{
    return nullptr;
}

storage::CopyOrMoveFileValidatorFactory*
TestFileSystemBackend::GetCopyOrMoveFileValidatorFactory(
    storage::FileSystemType type,
    base::File::Error* error_code)
{
    DCHECK(error_code);
    *error_code = base::File::FILE_OK;
    if (require_copy_or_move_validator_) {
        if (!copy_or_move_file_validator_factory_)
            *error_code = base::File::FILE_ERROR_SECURITY;
        return copy_or_move_file_validator_factory_.get();
    }
    return nullptr;
}

void TestFileSystemBackend::InitializeCopyOrMoveFileValidatorFactory(
    std::unique_ptr<storage::CopyOrMoveFileValidatorFactory> factory)
{
    if (!copy_or_move_file_validator_factory_)
        copy_or_move_file_validator_factory_ = std::move(factory);
}

FileSystemOperation* TestFileSystemBackend::CreateFileSystemOperation(
    const FileSystemURL& url,
    FileSystemContext* context,
    base::File::Error* error_code) const
{
    std::unique_ptr<FileSystemOperationContext> operation_context(
        new FileSystemOperationContext(context));
    operation_context->set_update_observers(*GetUpdateObservers(url.type()));
    operation_context->set_change_observers(*GetChangeObservers(url.type()));
    return FileSystemOperation::Create(url, context,
        std::move(operation_context));
}

bool TestFileSystemBackend::SupportsStreaming(
    const storage::FileSystemURL& url) const
{
    return false;
}

bool TestFileSystemBackend::HasInplaceCopyImplementation(
    storage::FileSystemType type) const
{
    return true;
}

std::unique_ptr<storage::FileStreamReader>
TestFileSystemBackend::CreateFileStreamReader(
    const FileSystemURL& url,
    int64_t offset,
    int64_t max_bytes_to_read,
    const base::Time& expected_modification_time,
    FileSystemContext* context) const
{
    return std::unique_ptr<storage::FileStreamReader>(
        storage::FileStreamReader::CreateForFileSystemFile(
            context, url, offset, expected_modification_time));
}

std::unique_ptr<storage::FileStreamWriter>
TestFileSystemBackend::CreateFileStreamWriter(
    const FileSystemURL& url,
    int64_t offset,
    FileSystemContext* context) const
{
    return std::unique_ptr<storage::FileStreamWriter>(
        new storage::SandboxFileStreamWriter(context, url, offset,
            *GetUpdateObservers(url.type())));
}

storage::FileSystemQuotaUtil* TestFileSystemBackend::GetQuotaUtil()
{
    return quota_util_.get();
}

const storage::UpdateObserverList* TestFileSystemBackend::GetUpdateObservers(
    storage::FileSystemType type) const
{
    return &update_observers_;
}

const storage::ChangeObserverList* TestFileSystemBackend::GetChangeObservers(
    storage::FileSystemType type) const
{
    return &change_observers_;
}

const storage::AccessObserverList* TestFileSystemBackend::GetAccessObservers(
    storage::FileSystemType type) const
{
    return nullptr;
}

void TestFileSystemBackend::AddFileChangeObserver(
    storage::FileChangeObserver* observer)
{
    change_observers_ = change_observers_.AddObserver(observer, task_runner_.get());
}

} // namespace content
